﻿#region Usings

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Common.Utility;
using Common.ViewModel;
using DataInterpolator.Model;
using DataInterpolator.Properties;
using DataInterpolator.Utility;
using DataInterpolator.ViewModel.Messages;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using OxyPlot;
using OxyPlot.Annotations;
using OxyPlot.Axes;

#endregion

namespace DataInterpolator.ViewModel
{
   /// <summary>
   ///    This class contains properties that a View can data bind to.
   ///    <para>
   ///       See http://www.galasoft.ch/mvvm
   ///    </para>
   /// </summary>
   public class InterpolationViewModel : ViewModelBase
   {
      public const double __MiddleAxisMinPad = 0.49;
      public const double __MiddleAxisMaxPad = 0.51;

      public const string __LeftKey = "Left";
      public const string __MiddleKey = "Middle";
      public const string __RightKey = "Right";
      private const string __VertLeftKey = "VerticalLeft";
      private const string __VertRightKey = "VerticalRight";

      /// <summary>
      ///    The <see cref="LeftWellTitle" /> property's name.
      /// </summary>
      public const string LeftWellTitlePropertyName = "LeftWellTitle";

      /// <summary>
      ///    The <see cref="RightWellTitle" /> property's name.
      /// </summary>
      public const string RightWellTitlePropertyName = "RightWellTitle";

      /// <summary>
      ///    The <see cref="WellTitleChoices" /> property's name.
      /// </summary>
      public const string WellTitleChoicesPropertyName = "WellTitleChoices";

      /// <summary>
      ///    The <see cref="SamplingDistance" /> property's name.
      /// </summary>
      public const string SamplingDistancePropertyName = "SamplingDistance";

      /// <summary>
      ///    The <see cref="InterpolationLevels" /> property's name.
      /// </summary>
      public const string InterpolationLevelsPropertyName = "InterpolationLevels";

      /// <summary>
      ///    The <see cref="XAxisMin" /> property's name.
      /// </summary>
      public const string XAxisMinPropertyName = "XAxisMin";

      /// <summary>
      ///    The <see cref="XAxisMax" /> property's name.
      /// </summary>
      public const string XAxisMaxPropertyName = "XAxisMax";

      private readonly IDataService _DataService;

      private readonly InterpHandlesViewModel _Handles;

      private MultiSelectBindingList<WellVMO> _InternalWellData;

      private ShiftedLinearAxis _LeftAxis;

      private string _LeftWellTitle = "";

      private RectangleAnnotation _MiddleRect;

      private RelayCommand _RecalculateCommand;

      private RelayCommand _ResetAllCommand;
      private ShiftedLinearAxis _RightAxis;

      private string _RightWellTitle = "";

      private LinearAxis _TopLeft;
      //private LinearAxis _BotLeft = null;
      private LinearAxis _TopMiddle;
      //private LinearAxis _BotMiddle = null;
      private LinearAxis _TopRight;

      private bool _UseXAxisDefaults = true;

      private BindingList<string> _WellTitleChoices = new BindingList<string>();

      private double _XAxisMax;

      private double _XAxisMin;
      //private LinearAxis _BotRight = null;

      /// <summary>
      ///    Initializes a new instance of the InterpolationViewModel class.
      /// </summary>
      public InterpolationViewModel(IDataService dataService)
      {
         _DataService = dataService;

         Messenger.Default.Register<FullRefreshMessage>(this, OnFullRefresh);
         Messenger.Default.Register<PartialUpdateMessage>(this, OnPartialUpdate);

         SetUpPlotViewModels();

         _Handles = new InterpHandlesViewModel(PlotViewModel);

         _DataService.SetInterpolationLevels(Settings.Default.InterpolationLevels);
         _DataService.SetSamplingDistance(Settings.Default.SampingDistance);

         Messenger.Default.Register<MenuCommandMessage>(this, OnMenuCommand);

         //Request any existing data
         Messenger.Default.Send(new RequestDataMessage(OnFullRefresh));
      }

      public MultiSelectBindingList<WellVMO> InternalWellData
      {
         get
         {
            return _InternalWellData;
         }
         set
         {
            //Need to move the event handlers
            if (_InternalWellData != null)
               _InternalWellData.ListChanged -= OnItemChanged;

            _InternalWellData = value;

            if (_InternalWellData != null)
               _InternalWellData.ListChanged += OnItemChanged;

            RaisePropertyChanged("InternalWellData");
         }
      }

      public PlotModel PlotViewModel { get; } = new PlotModel();

      /// <summary>
      ///    Sets and gets the LeftWellTitle property.
      ///    Changes to that property's value raise the PropertyChanged event.
      /// </summary>
      public string LeftWellTitle
      {
         get
         {
            return _LeftWellTitle;
         }

         set
         {
            if (_LeftWellTitle == value)
               return;

            _LeftWellTitle = value;
            RaisePropertyChanged(LeftWellTitlePropertyName);

            _DataService.SetLeftWell(value);
         }
      }

      /// <summary>
      ///    Sets and gets the RightWellTitle property.
      ///    Changes to that property's value raise the PropertyChanged event.
      /// </summary>
      public string RightWellTitle
      {
         get
         {
            return _RightWellTitle;
         }

         set
         {
            if (_RightWellTitle == value)
               return;

            _RightWellTitle = value;
            RaisePropertyChanged(RightWellTitlePropertyName);

            _DataService.SetRightWell(value);
         }
      }

      /// <summary>
      ///    Sets and gets the WellTitleChoices property.
      ///    Changes to that property's value raise the PropertyChanged event.
      /// </summary>
      public BindingList<string> WellTitleChoices
      {
         get
         {
            return _WellTitleChoices;
         }

         set
         {
            if (_WellTitleChoices == value)
               return;

            _WellTitleChoices = value;
            RaisePropertyChanged(WellTitleChoicesPropertyName);
         }
      }

      /// <summary>
      ///    Sets and gets the SamplingDistance property.
      ///    Changes to that property's value raise the PropertyChanged event.
      /// </summary>
      public double SamplingDistance
      {
         get
         {
            return Settings.Default.SampingDistance;
         }

         set
         {
            if (Settings.Default.SampingDistance == value)
               return;

            Settings.Default.SampingDistance = value;
            RaisePropertyChanged(SamplingDistancePropertyName);

            _DataService.SetSamplingDistance(Settings.Default.SampingDistance);
         }
      }

      /// <summary>
      ///    Sets and gets the InterpolationLevels property.
      ///    Changes to that property's value raise the PropertyChanged event.
      /// </summary>
      public int InterpolationLevels
      {
         get
         {
            return Settings.Default.InterpolationLevels;
         }

         set
         {
            if (Settings.Default.InterpolationLevels == value)
               return;

            Settings.Default.InterpolationLevels = value;
            RaisePropertyChanged(InterpolationLevelsPropertyName);

            UpdateAxesSpacing(value);

            _DataService.SetInterpolationLevels(Settings.Default.InterpolationLevels);
         }
      }

      /// <summary>
      ///    Sets and gets the UseXAxisDefaults property.
      ///    Changes to that property's value raise the PropertyChanged event.
      /// </summary>
      public bool UseXAxisDefaults
      {
         get
         {
            return _UseXAxisDefaults;
         }

         set
         {
            if (_UseXAxisDefaults == value)
               return;

            _UseXAxisDefaults = value;

            //If going from false to true, recalculate the interpolation to get the proper default values
            if (_UseXAxisDefaults)
               RecalculateCommand.Execute(null);

            RaisePropertyChanged("UseXAxisDefaults");
         }
      }

      /// <summary>
      ///    Sets and gets the XAxisMin property.
      ///    Changes to that property's value raise the PropertyChanged event.
      /// </summary>
      public double XAxisMin
      {
         get
         {
            return _XAxisMin;
         }

         set
         {
            if (_XAxisMin == value)
               return;

            _XAxisMin = value;

            //Must recalculate so the interpolated wells get recalculated properly
            RecalculateCommand.Execute(null);

            RaisePropertyChanged(XAxisMinPropertyName);
         }
      }

      /// <summary>
      ///    Sets and gets the XAxisMax property.
      ///    Changes to that property's value raise the PropertyChanged event.
      /// </summary>
      public double XAxisMax
      {
         get
         {
            return _XAxisMax;
         }

         set
         {
            if (_XAxisMax == value)
               return;

            _XAxisMax = value;

            //Must recalculate so the interpolated wells get recalculated properly
            RecalculateCommand.Execute(null);

            RaisePropertyChanged(XAxisMaxPropertyName);
         }
      }

      /// <summary>
      ///    Gets the RecalculateCommand.
      /// </summary>
      public RelayCommand RecalculateCommand
      {
         get
         {
            return _RecalculateCommand ??
                   (_RecalculateCommand =
                    new RelayCommand(() => { _DataService.SetInterpolationPairs(_Handles.GetInterpList()); }));
         }
      }

      /// <summary>
      ///    Gets the ResetAllCommand.
      /// </summary>
      public RelayCommand ResetAllCommand
      {
         get
         {
            return _ResetAllCommand ?? (_ResetAllCommand = new RelayCommand(() =>
            {
               _Handles.Clear();
               PlotViewModel.ResetAllAxes();
               PlotViewModel.InvalidatePlot(true);
            }));
         }
      }

      private void OnMenuCommand(MenuCommandMessage command)
      {
         if (command.CommandType == MenuCommandTypes.RefreshPlot)
         {
            foreach (var axis in PlotViewModel.Axes)
               axis.Reset();

            PlotViewModel.InvalidatePlot(true);
         }
      }

      private void _DataService_WellDataUpdated(PartialUpdateTypes updateType)
      {
         if ((updateType & PartialUpdateTypes.Interp) > 0)
            _DataService_InterpolatedValuesUpdated();
      }

      private void OnFullRefresh(FullRefreshMessage message)
      {
         InternalWellData = message.DataView.SourceCollection as MultiSelectBindingList<WellVMO>;

         RefreshWellTitleOptions();

         _DataService_InterpolatedValuesUpdated();
      }

      private void OnPartialUpdate(PartialUpdateMessage message)
      {
         if ((message.UpdateType & PartialUpdateTypes.Interp) > 0)
            _DataService_InterpolatedValuesUpdated();
      }

      private void OnItemChanged(object sender, ListChangedEventArgs e)
      {
         if (e.PropertyDescriptor.Name == WellBase.VisiblePropertyName)
            RefreshWellTitleOptions();
         else if (e.PropertyDescriptor.Name == WellBase.TitlePropertyName)
         {
            //Update Axis name
            RefreshWellTitleOptions();
         }
      }

      private void RefreshWellTitleOptions()
      {
         List<string> titles = new List<string>();

         foreach (var well in _InternalWellData)
            titles.Add(well.Title);

         //save the old options
         string oldLeft = LeftWellTitle;
         string oldRight = RightWellTitle;

         WellTitleChoices = new BindingList<string>(titles);

         int leftIndex = Math.Max(WellTitleChoices.IndexOf(LeftWellTitle), 0);

         int rightIndex = leftIndex + 1;

         if (WellTitleChoices.Contains(RightWellTitle))
            rightIndex = Math.Max(WellTitleChoices.IndexOf(RightWellTitle), 0);

         if (WellTitleChoices.Count >= leftIndex + 1)
            LeftWellTitle = WellTitleChoices[leftIndex];
         else
            LeftWellTitle = "";

         if (WellTitleChoices.Count >= rightIndex + 1)
            RightWellTitle = WellTitleChoices[rightIndex];
         else
            RightWellTitle = "";
      }

      private void GetBounds(List<DataSet> sets, out double minX, out double maxX, out double minY, out double maxY)
      {
         bool found = false;

         minX = double.MaxValue;
         maxX = double.MinValue;
         minY = double.MaxValue;
         maxY = double.MinValue;

         for (int i = 0; i < sets.Count; i++)
         {
            if (sets[i] != null)
            {
               found = true;

               minX = Math.Min(minX, sets[i].MinValues);
               maxX = Math.Max(maxX, sets[i].MaxValues);
               minY = Math.Min(minY, sets[i].MinX);
               maxY = Math.Max(maxY, sets[i].MaxX);
            }
         }

         if (!found)
         {
            minX = -1.0;
            maxX = 1.0;
            minY = 0;
            maxY = 200.0;
         }
      }

      private void _DataService_InterpolatedValuesUpdated()
      {
         PlotViewModel.Series.Clear();

         _DataService.GetInterpolatedValues((left, interp, right, leftTrueOffset, rightTrueOffset) =>
         {
            if (_LeftAxis != null)
               _LeftAxis.Shift = leftTrueOffset;

            if (_RightAxis != null)
               _RightAxis.Shift = rightTrueOffset;

            _Handles.XOffset = left.XOffset;
            _Handles.XScale = left.XSpacing;

            double minX = double.MaxValue;
            double maxX = double.MinValue;
            double minY = double.MaxValue;
            double maxY = double.MinValue;

            List<DataSet> sets = new List<DataSet>();
            sets.Add(left);
            sets.Add(right);

            //Get the bounds
            GetBounds(sets, out minX, out maxX, out minY, out maxY);

            //Cache the true center before we use any specified X axis limits
            double center = (minX + maxX) / 2.0;

            if (UseXAxisDefaults)
            {
               _XAxisMin = minX;
               _XAxisMax = maxX;
               RaisePropertyChanged(XAxisMinPropertyName);
               RaisePropertyChanged(XAxisMaxPropertyName);
            }
            else
            {
               if (XAxisMax > XAxisMin)
               {
                  minX = XAxisMin;
                  maxX = XAxisMax;
               }
            }

            if (left != null)
            {
               _Handles.LeftBounds = new Tuple<int, int>(left.FirstValidIdx, left.LastValidIdx);

               var leftSeries = GenerateSeries(left, 1.0, 0.0);
               leftSeries.XAxisKey = __LeftKey;
               leftSeries.YAxisKey = __VertLeftKey;
               leftSeries.Limit = center;
               leftSeries.YShift = leftTrueOffset;
               leftSeries.Title = "Left Well";
               PlotViewModel.Series.Add(leftSeries);
            }

            if (right != null)
            {
               _Handles.RightBounds = new Tuple<int, int>(right.FirstValidIdx, right.LastValidIdx);

               var rightSeries = GenerateSeries(right, 1.0, 0.0);
               rightSeries.XAxisKey = __RightKey;
               rightSeries.YAxisKey = __VertRightKey;
               rightSeries.Limit = center;
               rightSeries.YShift = rightTrueOffset;
               rightSeries.Title = "Right Well";
               PlotViewModel.Series.Add(rightSeries);
            }

            if (interp != null)
            {
               double scale = -1.0 / (maxX - minX);

               for (int i = 0; i < interp.Count; i++)
               {
                  double shift = i + 1.0 - center * scale;

                  var interpSeries = GenerateSeries(interp[i], scale, shift);
                  interpSeries.XAxisKey = __MiddleKey;
                  interpSeries.YAxisKey = __VertLeftKey;
                  interpSeries.Limit = i + 1.0;
                  interpSeries.XScale = 1.0 / scale;
                  interpSeries.XShift = -i * interpSeries.XScale - interpSeries.XScale + center;
                  interpSeries.YShift = (i + 1.0) / (interp.Count + 1.0) * (_RightAxis.Shift - _LeftAxis.Shift) +
                                        _LeftAxis.Shift;
                  interpSeries.Title = string.Format("{0:P1} Interpolation", (i + 1.0) / (interp.Count + 1.0));
                  PlotViewModel.Series.Add(interpSeries);
               }
            }

            SetBounds(minX, maxX, minY, maxY);
         });

         _Handles.FullRefresh();
      }

      private void SetBounds(double minX, double maxX, double minY, double maxY)
      {
         double pad = 0.0;

         if (maxX != minX)
            pad = (maxX - minX) * 0.05;
         else if (maxX != 0)
            pad = maxX * 0.05;
         else
            pad = 0.1;

         _TopLeft.Maximum = maxX + pad;
         _TopLeft.Minimum = minX - pad;

         _TopRight.Maximum = maxX + pad;
         _TopRight.Minimum = minX - pad;

         foreach (var axis in PlotViewModel.Axes)
         {
            if (axis.IsVertical())
            {
               if (axis.DataMinimum == axis.ActualMinimum)
               {
                  axis.Minimum = double.NaN;
                  axis.Reset();
               }

               if (axis.DataMaximum == axis.ActualMaximum)
               {
                  axis.Maximum = double.NaN;
                  axis.Reset();
               }
            }
         }
      }

      private void SetUpPlotViewModels()
      {
         _MiddleRect = new RectangleAnnotation();
         _MiddleRect.Fill = OxyColor.FromRgb(245, 245, 245);
         _MiddleRect.Layer = AnnotationLayer.BelowSeries;
         _MiddleRect.XAxisKey = __MiddleKey;
         _MiddleRect.MaximumX = 10 + __MiddleAxisMaxPad;
         _MiddleRect.MinimumX = __MiddleAxisMinPad;
         PlotViewModel.Annotations.Add(_MiddleRect);

         _LeftAxis = new ShiftedLinearAxis();
         _LeftAxis.Key = __VertLeftKey;
         _LeftAxis.MinimumPadding = 0.1;
         _LeftAxis.MaximumPadding = 0.1;
         _LeftAxis.StartPosition = 1.0;
         _LeftAxis.EndPosition = 0.0;
         _LeftAxis.Position = AxisPosition.Left;
         _LeftAxis.Title = "Depth";
         _LeftAxis.Unit = "ft";

         _LeftAxis.LabelFormatter = v =>
         {
            string format = string.Concat("{0:",
                                          _LeftAxis.ActualStringFormat ?? _LeftAxis.StringFormat ?? string.Empty,
                                          "}");
            return string.Format(format, v + _LeftAxis.Shift);
         };

         _RightAxis = new ShiftedLinearAxis();
         _RightAxis.Key = __VertRightKey;
         _RightAxis.StartPosition = 1.0;
         _RightAxis.EndPosition = 0.0;
         _RightAxis.Position = AxisPosition.Right;
         _RightAxis.Title = "Depth";
         _RightAxis.Unit = "ft";

         _RightAxis.LabelFormatter = v =>
         {
            string format = string.Concat("{0:",
                                          _RightAxis.ActualStringFormat ?? _RightAxis.StringFormat ?? string.Empty,
                                          "}");
            return string.Format(format, v + _RightAxis.Shift);
         };

         _LeftAxis.TransformChanged += (o, e) =>
         {
            LinearAxis left = o as LinearAxis;

            _RightAxis.AbsoluteMinimum = left.ActualMinimum;
            _RightAxis.Minimum = left.ActualMinimum;
            _RightAxis.AbsoluteMaximum = left.ActualMaximum;
            _RightAxis.Maximum = left.ActualMaximum;
            _RightAxis.MinorStep = left.ActualMinorStep;
            _RightAxis.MajorStep = left.ActualMajorStep;
            _RightAxis.Reset();

            _MiddleRect.MinimumY = left.ActualMinimum;
            _MiddleRect.MaximumY = left.ActualMaximum;
         };

         PlotViewModel.Axes.Add(_LeftAxis);
         PlotViewModel.Axes.Add(_RightAxis);

         _TopLeft = new LinearAxis();
         _TopLeft.Position = AxisPosition.Top;
         _TopLeft.Key = __LeftKey;
         _TopLeft.Title = "Left Axis DT1";
         _TopLeft.Unit = "μs/ft";
         _TopLeft.IsZoomEnabled = false;
         _TopLeft.IsPanEnabled = false;
         _TopLeft.StartPosition = 1.0;
         _TopLeft.EndPosition = 0.0;
         _TopLeft.MinimumPadding = 0.1;
         _TopLeft.MaximumPadding = 0.1;
         PlotViewModel.Axes.Add(_TopLeft);

         _TopMiddle = new LinearAxis();
         _TopMiddle.Position = AxisPosition.Top;
         _TopMiddle.Key = __MiddleKey;
         _TopMiddle.Title = "Interpolation Index";
         _TopMiddle.IsZoomEnabled = false;
         _TopMiddle.IsPanEnabled = false;
         _TopMiddle.MinorTickSize = 0;
         _TopMiddle.MinorStep = 1.0;
         _TopMiddle.MajorStep = 1.0;
         _TopMiddle.MinimumPadding = 0.1;
         _TopMiddle.MaximumPadding = 0.1;
         PlotViewModel.Axes.Add(_TopMiddle);

         _TopRight = new LinearAxis();
         _TopRight.Position = AxisPosition.Top;
         _TopRight.Key = __RightKey;
         _TopRight.Title = "Right Axis DT1";
         _TopRight.Unit = "μs/ft";
         _TopRight.IsZoomEnabled = false;
         _TopRight.IsPanEnabled = false;
         _TopRight.StartPosition = 1.0;
         _TopRight.EndPosition = 0.0;
         _TopRight.MinimumPadding = 0.1;
         _TopRight.MaximumPadding = 0.1;
         PlotViewModel.Axes.Add(_TopRight);

         UpdateAxesSpacing(Settings.Default.InterpolationLevels);

         PlotViewModel.IsLegendVisible = false;

         PlotViewModel.InvalidatePlot(true);
      }

      private void UpdateAxesSpacing(int interpCount)
      {
         double interval = 1.0 / (interpCount + 2.0);

         _TopLeft.StartPosition = interval;
         _TopLeft.EndPosition = 0;

         _TopMiddle.StartPosition = interval;
         _TopMiddle.EndPosition = 1.0 - interval;

         _TopRight.StartPosition = 1.0;
         _TopRight.EndPosition = 1.0 - interval;

         _TopMiddle.Minimum = __MiddleAxisMinPad;
         _TopMiddle.AbsoluteMinimum = _TopMiddle.Minimum;
         _TopMiddle.Maximum = interpCount + __MiddleAxisMaxPad;
         _TopMiddle.AbsoluteMaximum = _TopMiddle.Maximum;

         if (_Handles != null)
            _Handles.FullRefresh();
      }

      private ShiftedTwoColorAreaSeries GenerateSeries(DataSet data, double xScale, double xOffset)
      {
         var series = new ShiftedTwoColorAreaSeries();

         series.Color = OxyColors.Tomato;
         series.Color2 = OxyColors.LightBlue;
         series.MarkerFill = OxyColors.Tomato;
         series.MarkerFill2 = OxyColors.LightBlue;
         series.StrokeThickness = 2;
         series.Limit = 0;
         series.Smooth = false;
         series.UseYAxisLimit = false;
         series.CanTrackerInterpolatePoints = false;
         series.XLabel = "DT1";
         series.YLabel = "Depth";

         for (int i = 0; i < data.Values.Count; i++)
         {
            if (double.IsNaN(data.Values[i]))
               continue;

            series.Points.Add(new DataPoint(data.Values[i] * xScale + xOffset, i * data.XSpacing + data.XOffset));
         }

         return series;
      }
   }
}
